package com.liujit.auth.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONObject;
import com.liujit.auth.domain.param.LoginParam;
import com.liujit.auth.domain.param.RefreshTokenParam;
import com.liujit.auth.domain.result.OauthToken;
import com.liujit.auth.security.enums.GrantType;
import com.liujit.common.constants.AuthConstant;
import com.liujit.common.constants.RedisKeyConstant;
import com.liujit.redis.service.RedisService;
import com.liujit.web.utils.JwtUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import java.security.Principal;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author: liujun
 * @date: 2021/6/21 10:41 下午
 * @description: 认证接口
 */
@RequiredArgsConstructor
@Api(tags = "认证接口")
@RestController
public class OauthController {

    private final TokenEndpoint tokenEndpoint;

    private final RedisService redisService;

    @SneakyThrows
    @ApiOperation(value = "OAuth2认证", notes = "根据登录请求参数进行认证")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "param", value = "登录请求参数", dataType = "LoginParam", dataTypeClass = LoginParam.class, paramType = "body") })
    @PostMapping("/oauth/token")
    public OauthToken postAccessToken(@ApiIgnore Principal principal, @RequestBody LoginParam param) {
        /**
         * 获取登录认证的客户端ID
         *
         * 兼容两种方式获取Oauth2客户端信息（client_id、client_secret）
         * 方式一：client_id、client_secret放在请求路径中
         * 方式二：放在请求头（Request Headers）中的Authorization字段，且经过加密，例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
         */
        Map<String, String> parameters = MapUtil.newHashMap(8);
        parameters.put("grant_type", GrantType.PASSWORD.type());
        parameters.put("username", param.getAccount());
        parameters.put("password", param.getPwd());
        ResponseEntity<OAuth2AccessToken> entity = tokenEndpoint.postAccessToken(principal, parameters);
        return OauthToken.accessToken(entity.getBody());
    }

    @SneakyThrows
    @ApiOperation(value = "刷新Token", notes = "根据刷新Token请求参数进行刷新Token")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "param", value = "刷新Token请求参数", dataType = "RefreshTokenParam", dataTypeClass = RefreshTokenParam.class, paramType = "body") })
    @PostMapping("/oauth/token/refresh")
    public Object refreshToken(@ApiIgnore Principal principal, @RequestBody RefreshTokenParam param) {
        Map<String, String> parameters = MapUtil.newHashMap(8);
        parameters.put("grant_type", GrantType.REFRESH_TOKEN.type());
        parameters.put("refresh_token", param.getRefreshToken());
        ResponseEntity<OAuth2AccessToken> entity = tokenEndpoint.postAccessToken(principal, parameters);
        return OauthToken.accessToken(entity.getBody());
    }

    @ApiOperation(value = "退出登录", notes = "退出登录，token加入黑名单")
    @PostMapping("/logout")
    public Boolean logout() {
        // 获取负载
        JSONObject payload = JwtUtil.getJwtPayload();
        // JWT唯一标识
        String jti = payload.getStr(AuthConstant.JWT_JTI);
        // JWT过期时间戳(单位：秒)
        Long expireTime = payload.getLong(AuthConstant.JWT_EXP);
        // 缓存key
        String key = RedisKeyConstant.TOKEN_BLACKLIST + jti;
        if (expireTime != null) {
            // 当前时间（单位：秒）
            long currentTime = DateUtil.currentSeconds();
            // token未过期，添加至缓存作为黑名单限制访问，缓存时间为token过期剩余时间
            if (expireTime > currentTime) {
                redisService.set(key, payload, (expireTime - currentTime), TimeUnit.SECONDS);
            }
        } else {
            // token 永不过期则永久加入黑名单
            redisService.set(key, payload);
        }
        return Boolean.TRUE;
    }
}
